import logging
import os
import sys
import threading
import time
import typing
import uuid
from inspect import isfunction
from queue import Queue

from .PSData import PSData


class PSDataServer(object):
    '''
    类说明：
        处理 PSData 的类，每一个 GUI 控件都继承了了本类，用户调用本类的
        函数来对 UI 进行操作，为了保持各个 GUI 控件最重要的特性-灵活，要求
        本类的函数参数都是可以序列化的，如果这样的话，每一个控件就需要文
        档来约束入参和回参，在每个控件的构造函数中会注释好接收的入参类型
    版本：
        2021年11月17日 赵树成 V1.0  实现了大部分功能
    '''
    PSDATASERVERROOT = dict()
    PSDATASUBMANAGER = dict()
    PSDATASUBUUIDTABLE = dict()
    def __init__(self) -> None:
        super().__init__()
    
    def PsSetDataSync(self, data: 'PSData') -> 'bool':
        '''
        Funtion: 
            同步的设置数据
        args:
            data 要设置的数据，基类没有实现，需要继承后重写改函数
        return:
            如果设置成功，就返回 True
            如果设置失败，就返回 False
        '''
        if (hasattr(self, '_id') and hasattr(self, '_instance')):
            logging.error('Please Implement Function PsSetDataSync of serviceId={}, instanceId={}'.format(self._id, self._instance))
        else:
            logging.error('Please Implement Function PsSetDataSync')
        return False

    def PsSetDataAsync(self, data: 'PSData', func: typing.Union[typing.Callable[['bool'], None], None] = None) -> None:
        '''
        Funtion: 
            异步的设置数据
        args:
            data: 'PSData' 要设置的数据
            func: typing.Union[typing.Callable[['bool'], None], None] 设置完成后会调用此回调函数，入参是是否设置成功了,不设置也可以，那就什么也不返回
        return:
            None
        '''
        if (hasattr(self, '_queueSetValue')):
            self._queueSetValue.put((data, func), block=True)
        else:
            logging.warning('serviceId={0}, instanceId={1} don\'t support async set')

    def PsServerEnable(self, id: 'int', instance: 'int' = 0x00, enable_async: 'bool' = True) -> None:
        '''
        Funtion: 
            将自己根据 id 注册到根数据管理和根注册管理中
            如果有相同 id 的两个窗体服务
            则使用 instance 来区分不同的服务
        args:
            id 'int' 窗体服务的 ID
            instance 'int' 窗体服务的 instance,如果输入是0x00(默认)，系统会默认分配instance_id
            enable_async 'bool' 是否使能异步 set
        return:
            None
        raise:
            已经有相同服务的 id 和 instance了 -> raise 窗体服务冲突
        '''
        if (not hasattr(self, '_isPsServiceStart')):
            self._PsServerEnable(id, instance, enable_async)
        else:
            if (self._isPsServiceStart):
                logging.warn('Service id{0} instance{1} has been started!'.format(id, instance))
                pass
            else:
                self._PsServerEnable(id, instance, enable_async)
        self.EnableSystemEvent()

    def PsGetDataSync(self, data: 'PSData') -> 'PSData':
        '''
        Funtion: 
            同步的获取本对象的数据，如果获取成功，本对象会将入参的 PSData 填充然后返回
            如果获取失败，则会返回 None
            类似于HTTP的请求头
        args:
            data: 'PSData' 希望获取的数据
        return:
            typing.Union['PSData', None] 
                如果获取成功则填充 data 后返回该 data
                如果获取失败则返回 None
        raise:
            None
        '''
        return data
        


    def PsServerDisable(self) -> None:
        '''
        Funtion: 
            禁能自己的服务，调用此函数后其他节点将不能通过SOA的方式访问到自己
                - 注销所有自己注册的Event
                - 删除自己在 PSDATASUBMANAGER 中的数据
                - 删除在即在 PSDATASERVERROOT 中的数据
        args:
            None
        return:
            None
        raise:
            None
        '''
        self._isPsServiceStart = False
        # aa = 1
        # self.PsSignOutEventTo()
        if (hasattr(self, '_signInUuidTable')):
            for uuid_, v_ in self._signInUuidTable.items():
                self.PsSignOutEventTo(uuid_)
                # del self._signInUuidTable[uuid_]
        self._RemoveFromDict(self.PSDATASUBMANAGER)
        self._RemoveFromDict(self.PSDATASERVERROOT)
        


    def PsSetDataToSync(self, id: 'int', instance: 'int', data: 'PSData') -> 'bool':
        '''
        Funtion: 
            向指定的窗体服务同步的发送数据
        args:
            id 'int' 窗体服务的 ID
            instance 'int' 窗体服务的 instance
            data: 'PSData' 要发送的数据
        return:
            None
        raise:
            None
        '''
        obj_ = self._FindOtherServer(id, instance)
        data.args['_serviceId'] = self._id
        data.args['_instanceId'] = self._instance
        if (isinstance(obj_, PSDataServer)):
            return obj_.PsSetDataSync(data)
        return False


    def PsGetDataFromSync(self, id: 'int', instance: 'int', data: 'PSData') -> 'PSData':
        '''
        Funtion: 
            向指定的窗体服务同步的获取数据
        args:
            id 'int' 窗体服务的 ID
            instance 'int' 窗体服务的 instance
            data: 'PSData' 要获取的数据
        return:
            None
        raise:
            None
        '''
        obj_ = self._FindOtherServer(id, instance)
        data.args['_serviceId'] = self._id
        data.args['_instanceId'] = self._instance
        if (isinstance(obj_, PSDataServer)):
            return obj_.PsGetDataSync(data)
        return data

    def PsSetDataToAsync(self, id: 'int', instance: 'int', data: 'PSData', func: typing.Union[typing.Callable[['bool'], None], None] = None) -> None:
        '''
        Funtion: 
            向指定的窗体服务异步的设置数据
        args:
            id 'int' 窗体服务的 ID
            instance 'int' 窗体服务的 instance
            data: 'PSData' 要获取的数据
            func: typing.Callable[['bool'], None] 如果设置成功，就会给这个回调函数输入True，否则回复False，如果不输入就什么也不返回
        return:
            None
        raise:
            None
        '''
        obj_ = self._FindOtherServer(id, instance)
        data.args['_serviceId'] = self._id
        data.args['_instanceId'] = self._instance
        if (isinstance(obj_, PSDataServer)):
            obj_.PsSetDataAsync(data, func)

    def PsSignInEventTo(self, service_id: 'int', instance_id: 'int', data: 'PSData') -> typing.Union['str', None]:
        '''
        Function:
            向服务A注册函数，当服务A发生对应的事件时，调用本服务中的某个服务接口
            具体每个服务的 Event 事件需要开发者实现
        args:
            service_id 'int' 要订阅服务的 service_id
            instance_id 'int' 要订阅服务的 instance_id
            data 'PSData':
                .header = str('要注册的 Event 名字')
                .args = {
                    'set': str('要注册的 Event 发生的时候，调用本服务的哪个 Set 服务'),
                    'type': str('说明Event触发的类型，如 sync, async '),
                .value = 服务端规定的附加数据结构
                }
        return:
            'str' 如果注册成功，就返回 str 类型的UUID
            None 如果注册失败，就返回 None
        '''
        # 判断自己有没有 _id 和 _instance 属性，都有才继续
        if (not (hasattr(self, '_id') and hasattr(self, '_instance'))):
            logging.warning('self don\'t have attr _id and _instance, self.PSServer not enable?')
            return None
        data.args['id'] = self._id
        data.args['instance'] = self._instance
        # service_ = self._FindOtherServer(service_id, instance_id)
        # if (isinstance(service_, PSDataServer)):
            # TODO 添加对应的事件触发表
        # d_ = self.PSDATASUBMANAGER.get(service_id, None)
        # if (not isinstance(d_, dict)):
            # 如果找不到要注册服务的id,就返回
            # logging.warning('Cann\'t find service service_id={0}, instance_id={1} in RegisterMap'.format(service_id, instance_id))
            # return None
            # self.PSDATASUBMANAGER[service_id] = {}
        # d_ = d_.get(instance_id, None)
        # if (not isinstance(d_, dict)):
            # 如果找不到要注册服务的instance id，就返回
            # logging.warning('Cann\'t find service service_id={0}, instance_id={1} in RegisterMap'.format(service_id, instance_id))
            # return None
        # 添加服务注册信息
        if (service_id not in self.PSDATASUBMANAGER.keys()):
            self.PSDATASUBMANAGER[service_id] = {}
        d_ = self.PSDATASUBMANAGER[service_id]
        if (instance_id not in d_.keys()):
            d_[instance_id] = {}
        d_ = d_[instance_id]
        if (data.header not in d_.keys()):
            d_[data.header] = {}
        d_ = d_[data.header]
        uuid_ = uuid.uuid4().hex
        d_[uuid_] = data.args
        self.PSDATASUBUUIDTABLE[uuid_] = {
            'server': {
                'id': service_id,
                'instance': instance_id,
                'event': data.header,
            },
            'client': {
                'id': self._id,
                'instance': self._instance,
                # TODO 赵树成 考虑添加client端的详细信息
            },
        }
        if (not hasattr(self, '_signInUuidTable')):
            self._signInUuidTable = dict()
        self._signInUuidTable[uuid_] = dict()
        return uuid_
        # else:
        #     logging.warning('Cann\'t find service service_id={0}, instance_id={1}'.format(service_id, instance_id))
        #     return None

    def PsSignOutEventTo(self, uuid: 'str') -> 'bool':
        '''
        function:
            取消本对象对某一服务的事件注册
                - 删除self.PSDATASUBMANAGER 中对应Event的uuid字典
        args:
            uuid 'str' 期望删除的uuid
        return:
            bool 如果注销成功，返回True，如果注销失败，返回False
        '''
        d_ = self.PSDATASUBUUIDTABLE.get(uuid, None)
        if (not isinstance(d_, dict)):
            logging.warning('PSDATASUBUUIDTABLE don\'t have uuid={0}'.format(uuid))
            return False
        id_ = d_['server']['id']
        instance_ = d_['server']['instance']
        event_ = d_['server']['event']
        try:
            del self.PSDATASUBMANAGER[id_][instance_][event_][uuid]
            return True
        except:
            logging.error('PsSignOutEvent {0} fail!'.format(event_))
            return False

    def PsOnEvent(self, event: 'str', data: 'PSData') -> 'bool':
        '''
        function:
            触发事件，开发者在自己自定义的事件型函数中调用此函数

        args:
            event 'str' 事件的名字
            data 'PSData' 要设置的 PSData
        returns:
            如果所有的服务都回复了的True,代表所有的set都执行成功，此时回复此函数回复True
            否则回复False
        '''
        # 判断自己有没有 _id 和 _instance 属性，都有才继续
        if (not (hasattr(self, '_id') and hasattr(self, '_instance'))):
            logging.warning('self don\'t have attr _id and _instance, self.PSServer not enable?')
            return False
        d_ = self.PSDATASUBMANAGER.get(self._id, None)
        if (not isinstance(d_, dict)):
            logging.warning('PSDATASUBMANAGER don\'t have service_id={0}'.format(self._id))
            return False
        d_ = d_.get(self._instance, None)
        if (not isinstance(d_, dict)):
            logging.warning('PSDATASUBMANAGER service_id={0} don\'t have instance_id={1}'.format(self._id, self._instance))
            return False
        d_ = d_.get(event, None)
        if (isinstance(d_, dict)):
            l_ = list()
            res_collection_ = list()
            for uuid_, v_ in d_.items():
                l_.append(v_)
            for i_ in l_:
                data.header = i_['set']
                if (i_['type']) == 'sync':
                    # 如果是同步订阅，就调用该订阅者的同步设置函数
                    res_collection_.append(self.PsSetDataToSync(i_['id'], i_['instance'], data))
                elif (i_['type'] == 'async'):
                    # 如果是异步订阅，就调用该订阅者的异步设置函数
                    self.PsSetDataToAsync(i_['id'], i_['instance'], data)
                else:
                    # TODO 赵树成，如果后面有其他形式的调用方式，再补充
                    pass
            for res_ in res_collection_:
                if (not res_):
                    return False
            return True
        return False
        
    def PsAppendSystemServer(
        self, 
        service_id: 'int', 
        instance_id: 'int', 
        event_name_list: typing.List['str']) -> None:
        '''
        function:
            添加本服务的系统服务，添加的系统服务将会在本服务使能的时候
            主动订阅系统服务的event
        args:
            service_id 'int' 系统服务的serviceID
            instance_id 'int' 系统服务的instanceID
            event_name_list 'list' 期望订阅该系统服务的Event事件名
        return:
            None
        '''
        if (not hasattr(self, '_sysServer')):
            self._sysServer = dict()
        self._sysServer[(service_id, instance_id)] = event_name_list

    def PsIsServiceEnabled(self) -> 'bool':
        '''
        function:
            判断本服务是否开启了
        args:
            None
        return:
            bool 如果本服务开启了，则返回True，否则返回False
        '''
        if (not hasattr(self, '_isPsServiceStart')):
            return False
        return self._isPsServiceStart


    def _AddObjToDict(self, id: 'int', instance: 'int', d: 'dict', obj: typing.Any) -> None:
        if (id not in d.keys()):
            d[id] = dict()
        # else:
        #     logging.error('Have same service id {0}'.format(id))
        #     raise KeyError
        dd_ = d[id]
        if (instance in dd_.keys()):
            logging.error('Have same instance id {0}, which serviceId={1}'.format(id, instance))
            raise KeyError

        id_ = id
        instance_ = instance
        
        if (instance == 0x00):
            if (len(dd_) == 0):
                instance_ = 0x01
            else:
                instance_ = max(list(dd_.keys())) + 1
        dd_[instance_] = obj
        if (obj is self):
            self._id = id_
            self._instance = instance_

    def _RemoveFromDict(self, d: 'dict') -> None:
        if (hasattr(self, '_id') and hasattr(self, '_instance')):
            # 如果自己的类中有以上的属性，就把自己从字典中删除
            try:
                dd_ =  d[self._id]
                del dd_[self._instance]
                # if len(dd_):
                #     del d[self._id]
            except:
                logging.error('Remove Server id {0}, instance {1}'.format(self._id, self._instance))
        else:
            logging.error('Don\'t have attr id or instance')

    def _ReadQueue(self, queue: 'Queue') -> None:
        '''
        循环的读取队列
        '''
        while (True):
            if (self._isPsServiceStart):
                try:
                    data_ = queue.get(True, timeout=2)
                    if (isinstance(data_, tuple) and len(data_) == 2):
                        ps_data_ = data_[0] # PsData对象
                        func_ = data_[1]    # 异步回调函数对象
                        if (isinstance(ps_data_, PSData)):
                            res_ = self.PsSetDataSync(ps_data_)
                            if (hasattr(func_, '__call__')):
                                func_(res_)
                except:
                    pass
            else:
                break
        # print('在 id:{0} instance:{1} 的线程退出了'.format(self._id, self._instance))

    def _FindOtherServer(self, id: 'int', instance: 'int') -> typing.Union['PSDataServer', None]:
        '''
        function:
            根据 id 和 instance 寻找对应的 PSDataServer
        args:
            id 'int' 期望寻找的ID
            instance: 'int' 期望寻找的 instanceID
        returns:
            PsDataServer: 如果能找到对应的Server，就返回该Server
            None: 如果找不到对应的Server，则返回None
        '''
        if (id not in PSDataServer.PSDATASERVERROOT.keys()):
            logging.error('Can\'t find widget service id {0}'.format(id))
            return None
        else:
            obj_ = PSDataServer.PSDATASERVERROOT[id]
            if (instance not in obj_.keys()):
                logging.error('Can\'t find widget service instance {0} in id {1}'.format(instance, id))
                return None
            else:
                obj_ = obj_[instance]
                if (isinstance(obj_, PSDataServer)):
                    return obj_
        return None


    def _PsServerEnable(self, id: 'int', instance: 'int', enable_async: 'bool') -> None:
        '''
        Funtion: 
            将自己根据 id 注册到根数据管理和根注册管理中
            如果有相同 id 的两个窗体服务
            则使用 instance 来区分不同的服务
        args:
            id 'int' 窗体服务的 ID
            instance 'int' 窗体服务的 instance
        return:
            None
        raise:
            已经有相同服务的 id 和 instance了 -> raise 窗体服务冲突
        '''
        # 如果是 instanceId为0x00就会查找当前最大的InstanceId并自增
        self._AddObjToDict(id, instance, PSDataServer.PSDATASERVERROOT, self)
        self._AddObjToDict(id, instance, PSDataServer.PSDATASUBMANAGER, dict())
        self._isPsServiceStart = True
        if (enable_async):
            self._queueSetValue = Queue()       # 设置值的队列
            task_ = threading.Thread(target=self._ReadQueue, args=(self._queueSetValue,))
            task_.start()

    def EnableSystemEvent(self) -> None:
        # 订阅系统服务的事件
        if (hasattr(self, '_sysServer')):
            for k_, v_ in self._sysServer.items():
                for i_ in v_:
                    data_ = PSData(
                        header=i_,
                        args={
                            'set': i_,
                            'type': 'sync',
                        },
                        value={},
                    )
                    self.PsSignInEventTo(k_[0], k_[1], data_)

